/*
 * mm-naive.c - The fastest, least memory-efficient malloc package.
 *
 * In this naive approach, a block is allocated by simply incrementing
 * the brk pointer.  A block is pure payload. There are no headers or
 * footers.  Blocks are never umerged or reused. Realloc is
 * implemented directly using umalloc and ufree.
 *
 * NOTE TO STUDENTS: Replace this header comment with your own header
 * comment that gives a high level description of your solution.
 */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>

#include "mm.h"
#include "memlib.h"



/* single word (4) or double word (8) alignment */
#define ALIGNMENT 8

/* rounds up to the nearest multiple of ALIGNMENT */
#define ALIGN(size) (((size) + (ALIGNMENT-1)) & ~0x7)


#define SIZE_T_SIZE (ALIGN(sizeof(size_t)))

/*
    my macros
*/

/* Basic constants and macros */
#define WSIZE   4   /* word size */
#define DSIZE   8   /* double word size */
#define CHUNKSIZE   (1 << 12)   /* Extend heap by this amount (bytes) */

#define INITCHUNKSIZE   (1 << 7)

#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define MIN(x, y) ((x) < (y) ? (x) : (y))

/* Pack a size and allocated bit into a word */
#define PACK(size, alloc)   ((size) | (alloc))

/* Read and write a word at address p */
#define GET(p) (*(unsigned int *)(p))
#define PUT(p, val) (*(unsigned int *)(p) = (val))

/* Read size and allocated field */
#define GET_SIZE(p) (GET(p) & ~0x7)
#define GET_ALLOC(p) (GET(p) & 0x1)

/* Given block ptr bp, compute address of its header and footer */
#define HDRP(bp) ((char *)(bp) - WSIZE)
#define FTRP(bp) ((char *)(bp) + GET_SIZE(HDRP(bp)) - DSIZE)

/* Given block ptr bp, compute address of next and previous */
#define NEXT_BLKP(bp) ((char *)(bp) + GET_SIZE(HDRP(bp)))
#define PREV_BLKP(bp) ((char *)(bp) - GET_SIZE(((char *)(bp) - DSIZE)))

/* for explicit only */
/* Given blobk ptr bp, compute address of its prev and succ */
#define PREVP(bp) ((char *)(bp))
#define SUCCP(bp) ((char *)(bp) + WSIZE)
#define NEXT_FREE_BLKP(bp) (GET(SUCCP(bp)))
#define PREV_FREE_BLKP(bp) (GET(PREVP(bp)))

/* for segregated fit only */
#define FREE_LIST_SIZE 10
#define FREE_LIST_INDEX(bytes) (MIN(MAX(27 - __builtin_clz(bytes), 0), FREE_LIST_SIZE - 1))
#define FREELIST_NEXT(bp) (GET(bp))
#define NEXT_FREELIST_BLKP(bp) (GET(bp))

/* DEBUG LOG MACRO */
//#define LOG(code) (#ifdef DEBUG code #endif)



/* Debug option */
//#define DEBUG

/* version selection */
//#define IMPLICIT    // implicit list
//#define EXPLICIT    // explicit list
#define SEGREGATED   // segregated fit

static char *heap_listp;


static void add_to_free_list(size_t size, void *bp) {
    // printf("add_to_free\n");
    int index = FREE_LIST_INDEX(size);

    unsigned int *head = (unsigned int *)heap_listp + index;
    void *next = *head;

    PUT(PREVP(bp), head);
    PUT(SUCCP(bp), next);
    PUT(head, bp);



    if (next != NULL) {

        PUT(PREVP(next), bp);
    }
    // printf("end add_to_free\n");
}

static void pop_out_from_free_list(void *bp) {

    unsigned int *prev_free_bp = PREV_FREE_BLKP(bp);
    unsigned int *next_free_bp = NEXT_FREE_BLKP(bp);
    //printf("pop out %p %p\n", prev_free_bp, next_free_bp);
    // checking
    if (next_free_bp != NULL)
        PUT(PREVP(next_free_bp), prev_free_bp);

    unsigned gap = prev_free_bp - (unsigned int *)heap_listp;
    if (gap < FREE_LIST_SIZE)
        PUT(prev_free_bp, next_free_bp);
    else
        PUT(SUCCP(prev_free_bp), next_free_bp);

}

static void *umerge(void *bp) {

    size_t prev_alloc = GET_ALLOC(FTRP(PREV_BLKP(bp)));
    size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp)));
    size_t size = GET_SIZE(HDRP(bp));


    /* case 1 */
    if (prev_alloc && next_alloc) {
        
        add_to_free_list(size, bp);
    return bp;
    }


    /* case 2 */
    else if (prev_alloc && !next_alloc) {
        
        size += GET_SIZE(HDRP(NEXT_BLKP(bp)));

        #ifdef SEGREGATED
        void *next_bp = NEXT_BLKP(bp);
        pop_out_from_free_list(next_bp);
        add_to_free_list(size, bp);
        #endif
        PUT(HDRP(bp), PACK(size, 0));
        PUT(FTRP(bp), PACK(size, 0));
    }
    /* case 3 */
    else if (!prev_alloc && next_alloc) {
        
        size += GET_SIZE(HDRP(PREV_BLKP(bp)));
        #ifdef SEGREGATED
        void *prev_bp = PREV_BLKP(bp);
        pop_out_from_free_list(prev_bp);
        add_to_free_list(size, prev_bp);
        #endif
        PUT(FTRP(bp), PACK(size, 0));
        PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));
        bp = PREV_BLKP(bp);
    }
    /* case 4 */
    else {
        
        size += GET_SIZE((HDRP(PREV_BLKP(bp)))) + GET_SIZE(FTRP(NEXT_BLKP(bp)));
        #ifdef SEGREGATED
        void *next_bp = NEXT_BLKP(bp);
        pop_out_from_free_list(next_bp);
        void *prev_bp = PREV_BLKP(bp);
        pop_out_from_free_list(prev_bp);
        add_to_free_list(size, prev_bp);
        #endif
        PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));
        PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0));

        bp = PREV_BLKP(bp);
    }

    return bp;
}

static void *extend_heap(size_t words) {
    char *bp;
    size_t size;

   
    /* allocate an even number of words to maintain alignment */
    size = (words % 2) ? (words + 1) * WSIZE : words * WSIZE;

    if ((long)(bp = mem_sbrk(size)) == -1)
        return NULL;
   

    /* initialize free block header/footer and epilogue header */
    PUT(HDRP(bp), PACK(size, 0));
    PUT(FTRP(bp), PACK(size, 0));
    PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1));

    /* also initialize free blobk ptr */
   
    /* umerge if previous block was free */
   
    return umerge(bp);
}

static void *find_fit(size_t asize) {
    void *bp;
    long size;

    

    #ifdef SEGREGATED
    // printf("find_fit\n");
    int index = FREE_LIST_INDEX(asize);
    for (int i = index; i < FREE_LIST_SIZE; i++) {

        unsigned int *head = (unsigned int *)heap_listp + i;

        head = *head;
        int bestFitGap = 0x3f3f3f3f;
        void *bestFitPtr = head;
        while (head != NULL) {
            size = GET_SIZE(HDRP(head));
            if (size - asize <= bestFitGap) {
                bestFitGap = size - asize;
                bestFitPtr = head;
            }
            head = NEXT_FREE_BLKP(head);
        }
        if (bestFitGap != 0x3f3f3f3f)
            return bestFitPtr;
    }
    // printf("null\n");
    return NULL;
    #endif
}

static void *place(void *bp, size_t asize) {
    size_t csize = GET_SIZE(HDRP(bp));
   

    if ((csize - asize) >= 2 * DSIZE) {
        if (asize >= 96) {
        //printf("here=============\n");
        PUT(HDRP(bp), PACK(csize - asize, 0));
        PUT(FTRP(bp), PACK(csize - asize, 0));
        #ifdef SEGREGATED
        void *old_bp = bp;
        #endif
        bp = NEXT_BLKP(bp);
        PUT(HDRP(bp), PACK(asize, 1));
        PUT(FTRP(bp), PACK(asize, 1));
        #ifdef SEGREGATED
        pop_out_from_free_list(old_bp);
        add_to_free_list(csize - asize, old_bp);
        return bp;
        #endif
    }
        PUT(HDRP(bp), PACK(asize, 1));
        PUT(FTRP(bp), PACK(asize, 1));
      
        #ifdef SEGREGATED
        void *old_bp = bp;
        #endif
        bp = NEXT_BLKP(bp);
        PUT(HDRP(bp), PACK((csize - asize), 0));
        PUT(FTRP(bp), PACK((csize - asize), 0));
       
        #ifdef SEGREGATED

        pop_out_from_free_list(old_bp);
        add_to_free_list(csize - asize, bp);
        return old_bp;
        #endif
    } else {
        PUT(HDRP(bp), PACK(csize, 1));
        PUT(FTRP(bp), PACK(csize, 1));
       
        #ifdef SEGREGATED
        pop_out_from_free_list(bp);
        return bp;
        #endif

    }
}


void show_mem(void) {
    void *bp = (void *)heap_listp;
    long size;
    int i = 1;

   

    #ifdef SEGREGATED
    printf("---show_mem---\n");
    for (int i = 0; i < FREE_LIST_SIZE; i++) {
        unsigned int *head = (unsigned int *)heap_listp + i;
        head = *head;
        printf("free list [%d]: ", i);
        int j = 0;
        while (head != NULL && j++ < 10) {
            printf("block size = %d alloc = %d ", GET_SIZE(HDRP(head)), GET_ALLOC(HDRP(head)));
            head = NEXT_FREE_BLKP(head);
        }
        printf("\n");
    }
    printf("---end_mem---\n");
    #endif
}


/*
 * mm_init - initialize the malloc package.
 */
int mm_init(void)
{
    
    
    #ifdef SEGREGATED
    if ((heap_listp = mem_sbrk((4 + FREE_LIST_SIZE) * WSIZE)) == (void *)-1)
        return -1;
    PUT(heap_listp, 0);                             /* Alignment Padding */
    PUT(heap_listp + (1 * WSIZE), PACK(WSIZE * (FREE_LIST_SIZE + 2), 1));  /* Prologue header */
    for (int i = 0; i < FREE_LIST_SIZE; i++) {
        PUT(heap_listp + (i + 2) * WSIZE, NULL);
    }
    PUT(heap_listp + ((FREE_LIST_SIZE + 2) * WSIZE), PACK(WSIZE * (FREE_LIST_SIZE + 2), 1));  /* Prologue footer */
    PUT(heap_listp + ((FREE_LIST_SIZE + 3) * WSIZE), PACK(0, 1));      /* Epilogue header */

    heap_listp += (2 * WSIZE);
    //show_mem();
    /* Extend the empty heap with a free block of CHUNKSIZE bytes */
    if (extend_heap(INITCHUNKSIZE / WSIZE) == NULL)
        return -1;
    
    return 0;
    #endif

}

/*
 * umalloc - Allocate a block by incrementing the brk pointer.
 *     Always allocate a block whose size is a multiple of the alignment.
 */
void *umalloc(size_t size)
{
    size_t asize;   /* adjusted block size */
    size_t extendsize;  /* amount to extend heap if no fit */
    char *bp;
    
    if (size == 0)
        return NULL;

    if (size <= DSIZE)
        asize = 2 * DSIZE;
    else
        asize = DSIZE * ((size + DSIZE + (DSIZE - 1)) / DSIZE);



    if ((bp = find_fit(asize)) != NULL) {

        bp = place(bp, asize);
        
        return bp;
    }

    extendsize = MAX(asize, CHUNKSIZE);
    if ((bp = extend_heap(extendsize / WSIZE)) == NULL)
        return NULL;
    #ifndef SEGREGATED
    place(bp, asize);
    #else
    return umalloc(size);
    #endif
    
    return bp;

}

/*
 * ufree - Freeing a block does nothing.
 */
void ufree(void *ptr)
{

    size_t size = GET_SIZE(HDRP(ptr));
   
    PUT(HDRP(ptr), PACK(size, 0));
    PUT(FTRP(ptr), PACK(size, 0));

    umerge(ptr);

   
}

